Manfaatkan kekuatan Bantuan Async Generator JavaScript untuk pembuatan, transformasi, dan manajemen aliran data yang efisien. Jelajahi contoh praktis dan kasus penggunaan nyata untuk membangun aplikasi asinkron yang tangguh.
Bantuan Async Generator JavaScript: Menguasai Pembuatan dan Manajemen Aliran Data
Pemrograman asinkron dalam JavaScript telah berkembang secara signifikan selama bertahun-tahun. Dengan diperkenalkannya Async Generator dan Async Iterator, pengembang mendapatkan alat yang kuat untuk menangani aliran data asinkron. Sekarang, Bantuan Async Generator JavaScript semakin meningkatkan kemampuan ini, menyediakan cara yang lebih efisien dan ekspresif untuk membuat, mengubah, dan mengelola aliran data asinkron. Panduan ini akan membahas dasar-dasar Bantuan Async Generator, mendalami fungsionalitasnya, dan menunjukkan aplikasi praktisnya dengan contoh-contoh yang jelas.
Memahami Async Generator dan Iterator
Sebelum mendalami Bantuan Async Generator, sangat penting untuk memahami konsep dasar dari Async Generator dan Async Iterator.
Async Generator
Async Generator adalah fungsi yang dapat dijeda dan dilanjutkan, menghasilkan nilai secara asinkron. Ini memungkinkan Anda untuk menghasilkan urutan nilai dari waktu ke waktu, tanpa memblokir thread utama. Async Generator didefinisikan menggunakan sintaksis async function*.
Contoh:
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // Mensimulasikan operasi asinkron
yield i;
}
}
// Penggunaan
const sequence = generateSequence(1, 5);
Async Iterator
Async Iterator adalah objek yang menyediakan metode next(), yang mengembalikan promise yang akan resolve ke sebuah objek yang berisi nilai berikutnya dalam urutan dan properti done yang menunjukkan apakah urutan tersebut telah selesai. Async Iterator dikonsumsi menggunakan loop for await...of.
Contoh:
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 500));
yield i;
}
}
async function consumeSequence() {
const sequence = generateSequence(1, 5);
for await (const value of sequence) {
console.log(value);
}
}
consumeSequence();
Memperkenalkan Bantuan Async Generator
Bantuan Async Generator adalah serangkaian metode yang memperluas fungsionalitas prototipe Async Generator. Mereka menyediakan cara yang mudah untuk memanipulasi aliran data asinkron, membuat kode lebih mudah dibaca dan dipelihara. Bantuan ini beroperasi secara lazy (malas), artinya mereka hanya memproses data saat dibutuhkan, yang dapat meningkatkan kinerja.
Bantuan Async Generator berikut ini umumnya tersedia (tergantung pada lingkungan JavaScript dan polyfill):
mapfiltertakedropflatMapreducetoArrayforEach
Eksplorasi Mendalam Bantuan Async Generator
1. `map()`
Bantuan map() mengubah setiap nilai dalam urutan asinkron dengan menerapkan fungsi yang disediakan. Ini mengembalikan Async Generator baru yang menghasilkan nilai-nilai yang telah diubah.
Sintaksis:
asyncGenerator.map(callback)
Contoh: Mengonversi aliran angka menjadi kuadratnya.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const squares = numbers.map(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100)); // Mensimulasikan operasi asinkron
return num * num;
});
for await (const square of squares) {
console.log(square);
}
}
processNumbers();
Kasus Penggunaan Dunia Nyata: Bayangkan mengambil data pengguna dari beberapa API dan perlu mengubah data tersebut ke dalam format yang konsisten. map() dapat digunakan untuk menerapkan fungsi transformasi pada setiap objek pengguna secara asinkron.
async function* fetchUsersFromMultipleAPIs(apiEndpoints) {
for (const endpoint of apiEndpoints) {
const response = await fetch(endpoint);
const data = await response.json();
for (const user of data) {
yield user;
}
}
}
async function processUsers() {
const apiEndpoints = [
'https://api.example.com/users1',
'https://api.example.com/users2'
];
const users = fetchUsersFromMultipleAPIs(apiEndpoints);
const normalizedUsers = users.map(async (user) => {
// Menormalisasi format data pengguna
return {
id: user.userId || user.id,
name: user.fullName || user.name,
email: user.emailAddress || user.email
};
});
for await (const normalizedUser of normalizedUsers) {
console.log(normalizedUser);
}
}
2. `filter()`
Bantuan filter() membuat Async Generator baru yang hanya menghasilkan nilai-nilai dari urutan asli yang memenuhi kondisi yang diberikan. Ini memungkinkan Anda untuk secara selektif menyertakan nilai dalam aliran yang dihasilkan.
Sintaksis:
asyncGenerator.filter(callback)
Contoh: Menyaring aliran angka untuk hanya menyertakan angka genap.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 10);
const evenNumbers = numbers.filter(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100));
return num % 2 === 0;
});
for await (const evenNumber of evenNumbers) {
console.log(evenNumber);
}
}
processNumbers();
Kasus Penggunaan Dunia Nyata: Memproses aliran entri log dan menyaring entri berdasarkan tingkat keparahannya. Misalnya, hanya memproses kesalahan dan peringatan.
async function* readLogFile(filePath) {
// Mensimulasikan pembacaan file log baris per baris secara asinkron
const logEntries = [
{ timestamp: '...', level: 'INFO', message: '...' },
{ timestamp: '...', level: 'ERROR', message: '...' },
{ timestamp: '...', level: 'WARNING', message: '...' },
{ timestamp: '...', level: 'INFO', message: '...' },
{ timestamp: '...', level: 'ERROR', message: '...' }
];
for (const entry of logEntries) {
await new Promise(resolve => setTimeout(resolve, 50));
yield entry;
}
}
async function processLogs() {
const logEntries = readLogFile('path/to/log/file.log');
const errorAndWarningLogs = logEntries.filter(async (entry) => {
return entry.level === 'ERROR' || entry.level === 'WARNING';
});
for await (const log of errorAndWarningLogs) {
console.log(log);
}
}
3. `take()`
Bantuan take() membuat Async Generator baru yang hanya menghasilkan n nilai pertama dari urutan asli. Ini berguna untuk membatasi jumlah item yang diproses dari aliran yang berpotensi tak terbatas atau sangat besar.
Sintaksis:
asyncGenerator.take(n)
Contoh: Mengambil 3 angka pertama dari aliran angka.
async function* generateNumbers(start) {
let i = start;
while (true) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i++;
}
}
async function processNumbers() {
const numbers = generateNumbers(1);
const firstThree = numbers.take(3);
for await (const num of firstThree) {
console.log(num);
}
}
processNumbers();
Kasus Penggunaan Dunia Nyata: Menampilkan 5 hasil pencarian teratas dari API pencarian asinkron.
async function* search(query) {
// Mensimulasikan pengambilan hasil pencarian dari API
const results = [
{ title: 'Result 1', url: '...' },
{ title: 'Result 2', url: '...' },
{ title: 'Result 3', url: '...' },
{ title: 'Result 4', url: '...' },
{ title: 'Result 5', url: '...' },
{ title: 'Result 6', url: '...' }
];
for (const result of results) {
await new Promise(resolve => setTimeout(resolve, 100));
yield result;
}
}
async function displayTopSearchResults(query) {
const searchResults = search(query);
const top5Results = searchResults.take(5);
for await (const result of top5Results) {
console.log(result);
}
}
4. `drop()`
Bantuan drop() membuat Async Generator baru yang melewatkan n nilai pertama dari urutan asli dan menghasilkan nilai-nilai yang tersisa. Ini adalah kebalikan dari take() dan berguna untuk mengabaikan bagian awal dari sebuah aliran.
Sintaksis:
asyncGenerator.drop(n)
Contoh: Melewatkan 2 angka pertama dari aliran angka.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const remainingNumbers = numbers.drop(2);
for await (const num of remainingNumbers) {
console.log(num);
}
}
processNumbers();
Kasus Penggunaan Dunia Nyata: Melakukan paginasi melalui dataset besar yang diambil dari API, dengan melewatkan hasil yang sudah ditampilkan.
async function* fetchData(url, pageSize, pageNumber) {
const offset = (pageNumber - 1) * pageSize;
// Mensimulasikan pengambilan data dengan offset
const data = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
{ id: 4, name: 'Item 4' },
{ id: 5, name: 'Item 5' },
{ id: 6, name: 'Item 6' },
{ id: 7, name: 'Item 7' },
{ id: 8, name: 'Item 8' }
];
const pageData = data.slice(offset, offset + pageSize);
for (const item of pageData) {
await new Promise(resolve => setTimeout(resolve, 100));
yield item;
}
}
async function displayPage(pageNumber) {
const pageSize = 3;
const allData = fetchData('api/data', pageSize, pageNumber);
const page = allData.drop((pageNumber - 1) * pageSize); // lewati item dari halaman sebelumnya
const results = page.take(pageSize);
for await (const item of results) {
console.log(item);
}
}
// Contoh penggunaan
displayPage(2);
5. `flatMap()`
Bantuan flatMap() mengubah setiap nilai dalam urutan asinkron dengan menerapkan fungsi yang mengembalikan Async Iterable. Kemudian, ia meratakan (flattens) Async Iterable yang dihasilkan menjadi satu Async Generator. Ini berguna untuk mengubah setiap nilai menjadi aliran nilai dan kemudian menggabungkan aliran-aliran tersebut.
Sintaksis:
asyncGenerator.flatMap(callback)
Contoh: Mengubah aliran kalimat menjadi aliran kata.
async function* generateSentences() {
const sentences = [
'This is the first sentence.',
'This is the second sentence.',
'This is the third sentence.'
];
for (const sentence of sentences) {
await new Promise(resolve => setTimeout(resolve, 200));
yield sentence;
}
}
async function* stringToWords(sentence) {
const words = sentence.split(' ');
for (const word of words) {
await new Promise(resolve => setTimeout(resolve, 50));
yield word;
}
}
async function processSentences() {
const sentences = generateSentences();
const words = sentences.flatMap(async (sentence) => {
return stringToWords(sentence);
});
for await (const word of words) {
console.log(word);
}
}
processSentences();
Kasus Penggunaan Dunia Nyata: Mengambil komentar untuk beberapa postingan blog dan menggabungkannya menjadi satu aliran untuk diproses.
async function* fetchBlogPostIds() {
const blogPostIds = [1, 2, 3]; // Mensimulasikan pengambilan ID postingan blog dari API
for (const id of blogPostIds) {
await new Promise(resolve => setTimeout(resolve, 100));
yield id;
}
}
async function* fetchCommentsForPost(postId) {
// Mensimulasikan pengambilan komentar untuk sebuah postingan blog dari API
const comments = [
{ postId: postId, text: `Comment 1 for post ${postId}` },
{ postId: postId, text: `Comment 2 for post ${postId}` }
];
for (const comment of comments) {
await new Promise(resolve => setTimeout(resolve, 50));
yield comment;
}
}
async function processComments() {
const postIds = fetchBlogPostIds();
const allComments = postIds.flatMap(async (postId) => {
return fetchCommentsForPost(postId);
});
for await (const comment of allComments) {
console.log(comment);
}
}
6. `reduce()`
Bantuan reduce() menerapkan sebuah fungsi terhadap akumulator dan setiap nilai dari Async Generator (dari kiri ke kanan) untuk mereduksinya menjadi satu nilai tunggal. Ini berguna untuk mengagregasi data dari aliran asinkron.
Sintaksis:
asyncGenerator.reduce(callback, initialValue)
Contoh: Menghitung jumlah angka dalam sebuah aliran.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const sum = await numbers.reduce(async (accumulator, num) => {
await new Promise(resolve => setTimeout(resolve, 100));
return accumulator + num;
}, 0);
console.log('Sum:', sum);
}
processNumbers();
Kasus Penggunaan Dunia Nyata: Menghitung waktu respons rata-rata dari serangkaian panggilan API.
async function* fetchResponseTimes(apiEndpoints) {
for (const endpoint of apiEndpoints) {
const startTime = Date.now();
try {
await fetch(endpoint);
const endTime = Date.now();
const responseTime = endTime - startTime;
await new Promise(resolve => setTimeout(resolve, 50));
yield responseTime;
} catch (error) {
console.error(`Error fetching ${endpoint}: ${error}`);
yield 0; // Atau tangani kesalahan dengan tepat
}
}
}
async function calculateAverageResponseTime() {
const apiEndpoints = [
'https://api.example.com/endpoint1',
'https://api.example.com/endpoint2',
'https://api.example.com/endpoint3'
];
const responseTimes = fetchResponseTimes(apiEndpoints);
let count = 0;
const sum = await responseTimes.reduce(async (accumulator, time) => {
count++;
return accumulator + time;
}, 0);
const average = count > 0 ? sum / count : 0;
console.log(`Average response time: ${average} ms`);
}
7. `toArray()`
Bantuan toArray() mengonsumsi Async Generator dan mengembalikan sebuah promise yang akan resolve menjadi sebuah array yang berisi semua nilai yang dihasilkan oleh generator. Ini berguna ketika Anda perlu mengumpulkan semua nilai dari aliran ke dalam satu array untuk pemrosesan lebih lanjut.
Sintaksis:
asyncGenerator.toArray()
Contoh: Mengumpulkan angka dari sebuah aliran ke dalam sebuah array.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const numberArray = await numbers.toArray();
console.log('Number Array:', numberArray);
}
processNumbers();
Kasus Penggunaan Dunia Nyata: Mengumpulkan semua item dari API yang terpaginasi ke dalam satu array untuk penyaringan atau pengurutan di sisi klien.
async function* fetchAllItems(apiEndpoint) {
let pageNumber = 1;
const pageSize = 100; // Sesuaikan berdasarkan batas paginasi API
while (true) {
const url = `${apiEndpoint}?page=${pageNumber}&pageSize=${pageSize}`;
const response = await fetch(url);
const data = await response.json();
if (!data || data.length === 0) {
break; // Tidak ada data lagi
}
for (const item of data) {
await new Promise(resolve => setTimeout(resolve, 50));
yield item;
}
pageNumber++;
}
}
async function processAllItems() {
const apiEndpoint = 'https://api.example.com/items';
const allItems = fetchAllItems(apiEndpoint);
const itemsArray = await allItems.toArray();
console.log(`Fetched ${itemsArray.length} items.`);
// Pemrosesan lebih lanjut dapat dilakukan pada `itemsArray`
}
8. `forEach()`
Bantuan forEach() mengeksekusi fungsi yang disediakan sekali untuk setiap nilai dalam Async Generator. Berbeda dengan bantuan lainnya, forEach() tidak mengembalikan Async Generator baru; ini digunakan untuk melakukan efek samping (side effects) pada setiap nilai.
Sintaksis:
asyncGenerator.forEach(callback)
Contoh: Mencatat setiap angka dalam aliran ke konsol.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
await numbers.forEach(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Number:', num);
});
}
processNumbers();
Kasus Penggunaan Dunia Nyata: Mengirim pembaruan real-time ke antarmuka pengguna saat data diproses dari sebuah aliran.
async function* fetchRealTimeData(dataSource) {
//Mensimulasikan pengambilan data real-time (misalnya harga saham).
const dataStream = [
{ timestamp: new Date(), price: 100 },
{ timestamp: new Date(), price: 101 },
{ timestamp: new Date(), price: 102 }
];
for (const dataPoint of dataStream) {
await new Promise(resolve => setTimeout(resolve, 500));
yield dataPoint;
}
}
async function updateUI() {
const realTimeData = fetchRealTimeData('stock-api');
await realTimeData.forEach(async (data) => {
//Mensimulasikan pembaruan UI
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Updating UI with data: ${JSON.stringify(data)}`);
// Kode untuk benar-benar memperbarui UI akan ada di sini.
});
}
Menggabungkan Bantuan Async Generator untuk Pipeline Data yang Kompleks
Kekuatan sebenarnya dari Bantuan Async Generator berasal dari kemampuannya untuk dirangkai bersama untuk membuat pipeline data yang kompleks. Ini memungkinkan Anda untuk melakukan beberapa transformasi dan operasi pada aliran asinkron dengan cara yang ringkas dan mudah dibaca.
Contoh: Menyaring aliran angka untuk hanya menyertakan angka genap, kemudian mengkuadratkannya, dan akhirnya mengambil 3 hasil pertama.
async function* generateNumbers(start) {
let i = start;
while (true) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i++;
}
}
async function processNumbers() {
const numbers = generateNumbers(1);
const processedNumbers = numbers
.filter(async (num) => num % 2 === 0)
.map(async (num) => num * num)
.take(3);
for await (const num of processedNumbers) {
console.log(num);
}
}
processNumbers();
Kasus Penggunaan Dunia Nyata: Mengambil data pengguna, menyaring pengguna berdasarkan lokasi mereka, mengubah data mereka untuk hanya menyertakan bidang yang relevan, dan kemudian menampilkan 10 pengguna pertama di peta.
async function* fetchUsers() {
// Mensimulasikan pengambilan pengguna dari database atau API
const users = [
{ id: 1, name: 'John Doe', location: 'New York', email: 'john.doe@example.com' },
{ id: 2, name: 'Jane Smith', location: 'London', email: 'jane.smith@example.com' },
{ id: 3, name: 'Ken Tan', location: 'Singapore', email: 'ken.tan@example.com' },
{ id: 4, name: 'Alice Jones', location: 'New York', email: 'alice.jones@example.com' },
{ id: 5, name: 'Bob Williams', location: 'London', email: 'bob.williams@example.com' },
{ id: 6, name: 'Siti Rahman', location: 'Singapore', email: 'siti.rahman@example.com' },
{ id: 7, name: 'Ahmed Khan', location: 'Dubai', email: 'ahmed.khan@example.com' },
{ id: 8, name: 'Maria Garcia', location: 'Madrid', email: 'maria.garcia@example.com' },
{ id: 9, name: 'Li Wei', location: 'Shanghai', email: 'li.wei@example.com' },
{ id: 10, name: 'Hans Müller', location: 'Berlin', email: 'hans.muller@example.com' },
{ id: 11, name: 'Emily Chen', location: 'Sydney', email: 'emily.chen@example.com' }
];
for (const user of users) {
await new Promise(resolve => setTimeout(resolve, 50));
yield user;
}
}
async function displayUsersOnMap(location, maxUsers) {
const users = fetchUsers();
const usersForMap = users
.filter(async (user) => user.location === location)
.map(async (user) => ({
id: user.id,
name: user.name,
location: user.location
}))
.take(maxUsers);
console.log(`Displaying up to ${maxUsers} users from ${location} on the map:`);
for await (const user of usersForMap) {
console.log(user);
}
}
// Contoh penggunaan:
displayUsersOnMap('New York', 2);
displayUsersOnMap('London', 5);
Polyfill dan Dukungan Browser
Dukungan untuk Bantuan Async Generator dapat bervariasi tergantung pada lingkungan JavaScript. Jika Anda perlu mendukung browser atau lingkungan yang lebih lama, Anda mungkin perlu menggunakan polyfill. Polyfill menyediakan fungsionalitas yang hilang dengan mengimplementasikannya dalam JavaScript. Beberapa pustaka polyfill tersedia untuk Bantuan Async Generator, seperti core-js.
Contoh menggunakan core-js:
// Impor polyfill yang diperlukan
require('core-js/features/async-iterator/map');
require('core-js/features/async-iterator/filter');
// ... impor bantuan lain yang dibutuhkan
Penanganan Kesalahan
Saat bekerja dengan operasi asinkron, sangat penting untuk menangani kesalahan dengan benar. Dengan Bantuan Async Generator, penanganan kesalahan dapat dilakukan menggunakan blok try...catch di dalam fungsi asinkron yang digunakan dalam bantuan.
Contoh: Menangani kesalahan saat mengambil data di dalam operasi map().
async function* fetchData(urls) {
for (const url of urls) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
yield data;
} catch (error) {
console.error(`Error fetching data from ${url}: ${error}`);
yield null; // Atau tangani kesalahan dengan tepat, mis., dengan menghasilkan objek kesalahan
}
}
}
async function processData() {
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
const dataStream = fetchData(urls);
const processedData = dataStream.map(async (data) => {
if (data === null) {
return null; // Meneruskan kesalahan
}
// Proses data
return data;
});
for await (const item of processedData) {
if (item === null) {
console.log('Skipping item due to error');
continue;
}
console.log('Processed Item:', item);
}
}
processData();
Praktik Terbaik dan Pertimbangan
- Evaluasi Malas (Lazy Evaluation): Bantuan Async Generator dievaluasi secara malas, artinya mereka hanya memproses data saat diminta. Ini dapat meningkatkan kinerja, terutama saat berhadapan dengan dataset besar.
- Penanganan Kesalahan: Selalu tangani kesalahan dengan benar di dalam fungsi asinkron yang digunakan dalam bantuan.
- Polyfill: Gunakan polyfill bila perlu untuk mendukung browser atau lingkungan yang lebih lama.
- Keterbacaan: Gunakan nama variabel dan komentar yang deskriptif untuk membuat kode Anda lebih mudah dibaca dan dipelihara.
- Kinerja: Perhatikan implikasi kinerja dari merangkai beberapa bantuan bersama-sama. Meskipun sifat malas membantu, perangkaian yang berlebihan masih dapat menimbulkan overhead.
Kesimpulan
Bantuan Async Generator JavaScript menyediakan cara yang kuat dan elegan untuk membuat, mengubah, dan mengelola aliran data asinkron. Dengan memanfaatkan bantuan ini, pengembang dapat menulis kode yang lebih ringkas, mudah dibaca, dan dapat dipelihara untuk menangani operasi asinkron yang kompleks. Memahami dasar-dasar Async Generator dan Iterator, bersama dengan fungsionalitas masing-masing bantuan, sangat penting untuk memanfaatkan alat-alat ini secara efektif dalam aplikasi dunia nyata. Baik Anda sedang membangun pipeline data, memproses data real-time, atau menangani respons API asinkron, Bantuan Async Generator dapat secara signifikan menyederhanakan kode Anda dan meningkatkan efisiensi keseluruhannya.